package com.geovis.emergency.spd.biz.pc.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.geovis.emergency.spd.biz.command.mapper.TfljMapper;
import com.geovis.emergency.spd.biz.command.service.IOdsZgtfwTyphoonService;
import com.geovis.emergency.spd.biz.command.service.ITEpDisasterService;
import com.geovis.emergency.spd.biz.command.service.TfService;
import com.geovis.emergency.spd.biz.custom.service.ITyphoonService;
import com.geovis.emergency.spd.biz.pc.mapper.PcSchedulingEventMapper;
import com.geovis.emergency.spd.biz.pc.mapper.PcSchedulingEventSituationMapper;
import com.geovis.emergency.spd.biz.pc.mapper.PcSchedulingEventStatisticsMapper;
import com.geovis.emergency.spd.biz.pc.service.*;
import com.geovis.emergency.spd.cache.DictCache;
import com.geovis.emergency.spd.entity.command.entity.OdsZgtfwTyphoon;
import com.geovis.emergency.spd.entity.command.entity.TEpDisaster;
import com.geovis.emergency.spd.entity.command.entity.Tf;
import com.geovis.emergency.spd.entity.command.entity.Tflj;
import com.geovis.emergency.spd.entity.command.pojo.dto.SchedulingEventDTO;
import com.geovis.emergency.spd.entity.command.pojo.vo.OdsTyphoonVO;
import com.geovis.emergency.spd.entity.pc.entity.*;

import com.geovis.emergency.spd.entity.pc.pojo.constant.PcDisasterConstant;
import com.geovis.emergency.spd.entity.pc.pojo.enums.EventStatisticsTypeEnum;
import com.geovis.emergency.spd.entity.pc.pojo.enums.EventTypeEnum;
import com.geovis.emergency.spd.entity.pc.pojo.enums.SourceTypeEnum;
import com.geovis.emergency.spd.entity.pc.pojo.enums.StatusEnum;
import com.geovis.emergency.spd.entity.pc.pojo.vo.PcSchedulingEventVO;
import com.geovis.emergency.spd.entity.pc.pojo.vo.SchedulingEventStatisticsVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Valid;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 应急预案指挥模块-指挥调度事件表 服务实现类
 * </p>
 *
 * @author Lonsho
 * @since 2022-10-25
*/

@Service
@RequiredArgsConstructor
@Slf4j
public class PcSchedulingEventServiceImpl extends ServiceImpl<PcSchedulingEventMapper, PcSchedulingEvent> implements IPcSchedulingEventService {
    private final PcSchedulingEventStatisticsMapper schedulingEventStatisticsMapper;
    private final PcSchedulingEventSituationMapper schedulingEventSituationMapper;
    private final ITEpDisasterService tepDisasterService;
    @Lazy
    @Autowired
    private IPcDrillService pcDrillService;
    private final IPcDisasterService pcDisasterService;

    private final IPcDisasterEventService pcDisasterEventService;
    @Lazy
    @Autowired
    private IPcDrillDisasterService pcDrillDisasterService;
    private final IPcDrillDisasterEventService pcDrillDisasterEventService;
    private final IOdsZgtfwTyphoonService odsZgtfwTyphoonService;

    private final TfService tfService;

    private final TfljMapper tfljMapper;

    private final IPcPlanService pcPlanService;

    @Override
    public PcSchedulingEvent getMainEventId(String eventId) {
        return baseMapper.selectOne(
                Wrappers.lambdaQuery(PcSchedulingEvent.class)
                        .eq(PcSchedulingEvent::getId, eventId)
                        .orderByDesc(PcSchedulingEvent::getCreateTime)
                        .last("limit 1")
        );
    }
    private PcSchedulingEvent getByEventId(String eventId) {
        return baseMapper.selectOne(
                Wrappers.lambdaQuery(PcSchedulingEvent.class)
                        .eq(PcSchedulingEvent::getEventId, eventId)
                        .orderByDesc(PcSchedulingEvent::getCreateTime)
                        .last("limit 1")
        );
    }

    @Override
    public String pushEvent(SchedulingEventDTO schedulingEventDTO,String typhoonPointId) {
        Long eventId = schedulingEventDTO.getEventId();
        Assert.isFalse(ObjectUtil.isAllEmpty(schedulingEventDTO.getEventId(), schedulingEventDTO.getTyphoonNo()), "指挥调度事件id和台风编号必须传一个");
        if (ObjectUtil.isNotNull(eventId)) {
            //启动安全生产事故事件
            TEpDisaster tepDisaster = tepDisasterService.getById(eventId);
            Assert.notNull(tepDisaster, "事件【{}】不存在", eventId);
            PcSchedulingEvent pcSchedulingEvent = getByEventId(String.valueOf(eventId));
            if (ObjectUtil.isNull(pcSchedulingEvent)) {
                pcSchedulingEvent = new PcSchedulingEvent();
                pcSchedulingEvent.setEventId(tepDisaster.getId());
                pcSchedulingEvent.setTitle(tepDisaster.getTitle());
                pcSchedulingEvent.setContent(tepDisaster.getDescription());
                pcSchedulingEvent.setEventAddress(tepDisaster.getAddress());
                pcSchedulingEvent.setAddress(tepDisaster.getAddress());
                pcSchedulingEvent.setLongitude(tepDisaster.getLon());
                pcSchedulingEvent.setLatitude(tepDisaster.getLat());
                pcSchedulingEvent.setType(tepDisaster.getDisasterType());
                pcSchedulingEvent.setLevel(tepDisaster.getDisasterLevel());
                pcSchedulingEvent.setSubmitDept(ObjectUtil.isNotNull(tepDisaster.getReceiveReportDept()) ? tepDisaster.getReceiveReportDept() : tepDisaster.getReceiveDeptTitle());
                pcSchedulingEvent.setSubmitTime(LocalDateTime.parse(tepDisaster.getSendTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                if (ObjectUtil.isNull(tepDisaster.getSendTime())){
                    pcSchedulingEvent.setSubmitTime(LocalDateTime.parse(tepDisaster.getCreateTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                }
                pcSchedulingEvent.setReceiveTime(LocalDateTime.parse(tepDisaster.getCreateTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                pcSchedulingEvent.setRegionCode(tepDisaster.getRegionCode());
                pcSchedulingEvent.setStatus(StatusEnum.FALSE.getCode());

            }
            //新流程，生成演练
            return createSchedulingDrill(pcSchedulingEvent,tepDisaster.getTitle(),tepDisaster.getDisasterType(),"海南省应急厅",tepDisaster,typhoonPointId);
        } else {
            //先校验之前是否启动过，如果是在配置的状态或进入演练的状态，则直接返回
            String drillId=getDrillIdByTyphoonNo(schedulingEventDTO.getTyphoonNo());
            if(drillId!=null)
            {
                return drillId;
            }

            //启动台风事件
            Tf typhoon = tfService.getByTyphoonNo(schedulingEventDTO.getTyphoonNo());
            Assert.notNull(typhoon, "台风【{}】不存在", schedulingEventDTO.getTyphoonNo());
            PcSchedulingEvent pcSchedulingEvent = new PcSchedulingEvent();
            pcSchedulingEvent.setTyphoonNo(typhoon.getTyphoonId());
            pcSchedulingEvent.setTitle(typhoon.getCnName());
            LocalDateTime receiveTime=LocalDateTime.parse(typhoon.getStartTime()+":00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            pcSchedulingEvent.setReceiveTime(receiveTime);
            pcSchedulingEvent.setStatus(StatusEnum.FALSE.getCode());
            pcSchedulingEvent.setContent("台风-"+pcSchedulingEvent.getTitle());
            pcSchedulingEvent.setSubmitDept("海南气象局");
            pcSchedulingEvent.setSubmitTime(LocalDateTime.now());
            //新流程，生成演练
            return createSchedulingDrill(pcSchedulingEvent,"台风“"+typhoon.getCnName()+"”","a_b_a","海南气象局",null,typhoonPointId);
        }
    }

    /**
     * 获取从指挥系统过来的，在配置的状态或者启动状态的演练
     */
    private String getDrillIdByTyphoonNo(String typhoonNo)
    {
        List<PcDrill> drills=pcDrillDisasterService.getDrillByTyphoonNo(typhoonNo);
        if(CollectionUtils.isEmpty(drills))
        {
            return null;
        }
        //目前的数据情况，应该是只有一条
        return drills.get(0).getId();
    }

    /**
     * 获取台风事件的预案id
     * @return
     */
    private String getPlanId()
    {
        List<PcPlan> plans=pcPlanService.list(Wrappers.lambdaQuery(PcPlan.class).eq(PcPlan::getType,"a_b_a").orderByAsc(PcPlan::getCreateTime));
        if(!CollectionUtils.isEmpty(plans))
        {
            return plans.get(0).getId();
        }
        return null;
    }

    /**
     * 生成演练
     */
    private String createSchedulingDrill(PcSchedulingEvent pcSchedulingEvent,String eventName,String eventType,String source,TEpDisaster tepDisaster,String typhoonPointId)
    {

        String planId=getPlanId();
        if(planId==null)
        {
            return null;
        }
        //生成演练
        LocalDateTime now=LocalDateTime.now();
        PcDrill pcDrill =new PcDrill();
        pcDrill.setName(eventName)
                .setContent(eventName)
                .setPlanStartTime(now)
                .setStatus("1")
                .setDrillType("1")
                .setPcPlanId(planId);
        pcDrillService.save(pcDrill);
        //PcSchedulingEvent关联drillId
        pcSchedulingEvent.setDrillId(pcDrill.getId());
        baseMapper.insert(pcSchedulingEvent);
        //这里要生成新的主事件（并生成一条事件消息）,如果已存在则不生成
        //todo 这里只考虑台风事件，后面要扩展突发事件/值班值守事件
        PcDisaster disaster=pcDisasterService.getOne(Wrappers.lambdaQuery(PcDisaster.class)
                .eq(PcDisaster::getTyphoonNo, pcSchedulingEvent.getTyphoonNo()).eq(PcDisaster::getName,eventName));
        PcDisasterEvent disasterEvent=null;
        //台风最新点位记录id

        if(disaster==null)
        {
            disaster=new PcDisaster();
            disaster.setEventType("1")
                    .setName(eventName)
                    .setType(eventType)
                    .setDataStatus("1")
                    .setPcPlanId(planId)
                    .setTyphoonNo(pcSchedulingEvent.getTyphoonNo())
                    .setIdx(0);
            pcDisasterService.save(disaster);
            disasterEvent=savePcDisasterEevent(disaster,eventName,source,now,tepDisaster,typhoonPointId);

        }
        else
        {
            List<PcDisasterEvent> events=pcDisasterEventService.list(Wrappers.lambdaQuery(PcDisasterEvent.class)
                    .eq(PcDisasterEvent::getPcDisasterId, disaster.getId()).orderByAsc(PcDisasterEvent::getCreateTime));
            if(!CollectionUtils.isEmpty(events))
            {
                disasterEvent=events.get(0);
            }
            else
            {
                disasterEvent=savePcDisasterEevent(disaster,eventName,source,now,tepDisaster,typhoonPointId);
            }

        }


        //绑定演练与主事件
        PcDrillDisaster drillDisaster=new PcDrillDisaster();
        BeanUtils.copyProperties(disaster,drillDisaster);
        drillDisaster.setId(null);
        drillDisaster.setPcDrillId(pcDrill.getId())
                .setPcDisasterId(disaster.getId())
                .setSourceType(SourceTypeEnum.NEW_EVENT.getCode())
                .setIdx(0)
                .setCreateTime(now);
        pcDrillDisasterService.save(drillDisaster);
        //绑定演练与主事件消息
        PcDrillDisasterEvent pde=new PcDrillDisasterEvent();
        BeanUtils.copyProperties(disasterEvent,pde);
        pde.setId(null);
        pde.setPcDrillId(pcDrill.getId());
        pde.setPcDisasterId(disaster.getId());
        pde.setPcDisasterEventId(disasterEvent.getId());
        pde.setPcDrillDisasterId(drillDisaster.getId());
        pde.setSourceType(SourceTypeEnum.NEW_EVENT.getCode());
        pde.setSubmitTime(now);//这里可能会重复启动，所以应该每一次演练启动都是最新的时间
        pde.setActualSubmitTime(now);
        pde.setStatus("1");//这里还没有启动演练，所以状态为待下发
        pcDrillDisasterEventService.save(pde);
        //生成统计信息
        saveStatistics(drillDisaster.getId(), pcDrill.getId(), tepDisaster==null ? 0:tepDisaster.getDeathNum(), tepDisaster==null ? 0:tepDisaster.getMissNum(),tepDisaster==null ? 0:tepDisaster.getInjuredNum());
        return pcDrill.getId();
    }

    private PcDisasterEvent savePcDisasterEevent(PcDisaster disaster,String eventName,String source,LocalDateTime now,TEpDisaster tepDisaster,String typhoonPointId)
    {
        PcDisasterEvent disasterEvent=new PcDisasterEvent();
        BeanUtils.copyProperties(disaster,disasterEvent);
        if(tepDisaster!=null)
        {
            BeanUtils.copyProperties(tepDisaster,disasterEvent);
            disasterEvent.setContent(tepDisaster.getDescription())
                    .setLatitude(tepDisaster.getLat())
                    .setLongitude(tepDisaster.getLon());
        }
        else
        {
            disasterEvent.setContent(eventName)
                    .setTitle(eventName)
                    .setAddress("中华人民共和国");//台风目前没有地址信息，先暂时写死
        }
        disasterEvent.setPcDisasterId(disaster.getId())
                .setSource(source)
                .setEventStage("2")
                .setResponseType("1")//这里需要讨论确认下
                .setLevel("3")
                .setSubmitTime(now)
//                .setResponseAction("启动指挥")
                .setTyphoonPointId(typhoonPointId)
                .setId(null);
        pcDisasterEventService.save(disasterEvent);
        return disasterEvent;
    }



    private void saveStatistics(String pcDrillDisasterId,String drillId,int deathNum,int missNum,int injuredNum){
        PcSchedulingEventStatistics pcSchedulingEventStatistics = new PcSchedulingEventStatistics();
        pcSchedulingEventStatistics.setDrillId(drillId);
        pcSchedulingEventStatistics.setPcDrillDisasterId(pcDrillDisasterId);
        pcSchedulingEventStatistics.setType(EventStatisticsTypeEnum.SCHEDULING_EVENT.getCode());
        pcSchedulingEventStatistics.setDeathNum(deathNum);
        pcSchedulingEventStatistics.setMissNum(missNum);
        pcSchedulingEventStatistics.setInjuredNum(injuredNum);
        schedulingEventStatisticsMapper.insert(pcSchedulingEventStatistics);
    }

    @Override
    public PcSchedulingEventVO getDetailEventByDrillId(String drillId) {
        PcSchedulingEvent pcSchedulingEvent = baseMapper.selectOne(
                Wrappers.lambdaQuery(PcSchedulingEvent.class)
                        .eq(PcSchedulingEvent::getDrillId, drillId)
                        .last("limit 1")
        );
        return getDetail(pcSchedulingEvent);
    }

    @Override
    public PcSchedulingEventVO detailEvent(String drillId) {
        PcSchedulingEvent pcSchedulingEvent=baseMapper.selectOne(
                Wrappers.lambdaQuery(PcSchedulingEvent.class)
                        .eq(PcSchedulingEvent::getDrillId, drillId)
        );

        return getDetail(pcSchedulingEvent);
    }



    private PcSchedulingEventVO getDetail(PcSchedulingEvent pcSchedulingEvent){
        if (ObjectUtil.isNotNull(pcSchedulingEvent)){

            PcSchedulingEventVO pcSchedulingEventVO = BeanUtil.toBean(pcSchedulingEvent, PcSchedulingEventVO.class);
            List<PcSchedulingEventStatistics> statistics = schedulingEventStatisticsMapper.selectList(
                    Wrappers.lambdaQuery(PcSchedulingEventStatistics.class)
                            .eq(StrUtil.isNotBlank(pcSchedulingEventVO.getDrillId()),PcSchedulingEventStatistics::getDrillId,pcSchedulingEventVO.getDrillId())
            );
            //计算死亡，失踪，受伤人数
            if(!CollectionUtils.isEmpty(statistics))
            {
                int deathNum=statistics.stream().mapToInt(PcSchedulingEventStatistics::getDeathNum).sum();
                int missNum=statistics.stream().mapToInt(PcSchedulingEventStatistics::getMissNum).sum();
                int injuredNum=statistics.stream().mapToInt(PcSchedulingEventStatistics::getInjuredNum).sum();
                SchedulingEventStatisticsVO deathVo=new SchedulingEventStatisticsVO();
                deathVo.setStatisticsType("死亡人数")
                        .setStatisticsCount(deathNum);
                SchedulingEventStatisticsVO missVo=new SchedulingEventStatisticsVO();
                missVo.setStatisticsType("失踪人数")
                        .setStatisticsCount(missNum);
                SchedulingEventStatisticsVO injuredVo=new SchedulingEventStatisticsVO();
                injuredVo.setStatisticsType("受伤人数")
                        .setStatisticsCount(injuredNum);

                pcSchedulingEventVO.setStatisticsList(
                        Arrays.asList(deathVo,missVo,injuredVo)
                );
            }
            if (StrUtil.isNotBlank(pcSchedulingEvent.getType())){
                String disasterType = DictCache.getDictValue(PcDisasterConstant.DISASTER_TYPE, pcSchedulingEvent.getType());
                if (StrUtil.isNotBlank(disasterType)){
                    pcSchedulingEventVO.setTypeName(disasterType);
                }
            }
            if (StrUtil.isNotBlank(pcSchedulingEvent.getLevel())){
                String eventLevel = DictCache.getDictValue(PcDisasterConstant.EVENT_LEVEL, pcSchedulingEvent.getLevel());
                if (StrUtil.isNotBlank(eventLevel)){
                    pcSchedulingEventVO.setLevelName(eventLevel);
                }
            }

            return pcSchedulingEventVO;
        }
        return null;
    }
}
